package uk.ac.manchester.cs.owl.ontologyrepositoryapi;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Author: Matthew Horridge<br>
 * The University of Manchester<br>
 * Bio-Health Informatics Group<br>
 * Date: 25/01/2011
 */
public class BioPortalOntologyRepository implements OntologyRepository {

	private final String apiKey;
	
    private static final String ID_ELEMENT_NAME = "ontologyId";

    private static final String DISPLAY_NAME_ELEMENT_NAME = "displayLabel";

    private static final String ABBREV_ELEMENT_NAME = "abbreviation";

    private static final String ENTRY_ELEMENT = "ontologyBean";

    private static final String FORMAT_ELEMENT_NAME = "format";
    
    private static final String VERSION_ELEMENT_NAME = "internalVersionNumber";


    private List<BioPortalRepositoryEntry> entries = new ArrayList<BioPortalRepositoryEntry>();

    public BioPortalOntologyRepository(String apiKey) {
        this.entries = new ArrayList<BioPortalRepositoryEntry>();
        this.apiKey = apiKey;
    }

    public String getName() {
        return null;
    }

    public List<OntologyRepositoryEntry> getEntries() {
        if(entries.isEmpty()) {
            refill();
        }
        return new ArrayList<OntologyRepositoryEntry>(entries);
    }

    public int getEntriesCount() {
        return entries.size();
    }

    public OntologyRepositoryEntry getEntry(int index) {
        return entries.get(index);
    }



    public void refill() {
        try {
            String request = "http://rest.bioontology.org/bioportal/ontologies?apikey=" + apiKey;
            URL url = URI.create(request).toURL();
            BufferedInputStream is = new BufferedInputStream(url.openStream());
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser parser = factory.newSAXParser();
            ResponseParser responseParser = new ResponseParser();
            parser.parse(is, responseParser);
            List<BioPortalRepositoryEntry> entries = responseParser.getEntries();
            Collections.sort(entries, new Comparator<OntologyRepositoryEntry>() {
                public int compare(OntologyRepositoryEntry o1, OntologyRepositoryEntry o2) {
                    return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName());
                }
            });
            this.entries.clear();
            this.entries.addAll(entries);
        }
        catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
        catch (SAXException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    private class ResponseParser extends DefaultHandler {


        private StringBuilder sb = new StringBuilder();

        private String ontologyId;

        private String displayName;

        private String abbrevName;

        private String format;
        
        private String internalVersionNumber;

        private List<BioPortalRepositoryEntry> entries = new ArrayList<BioPortalRepositoryEntry>();


        /**
         * Receive notification of the start of an element.
         * <p>By default, do nothing.  Application writers may override this
         * method in a subclass to take specific actions at the start of
         * each element (such as allocating a new tree node or writing
         * output to a file).</p>
         * @param uri The Namespace URI, or the empty string if the
         * element has no Namespace URI or if Namespace
         * processing is not being performed.
         * @param localName The local name (without prefix), or the
         * empty string if Namespace processing is not being
         * performed.
         * @param qName The qualified name (with prefix), or the
         * empty string if qualified names are not available.
         * @param attributes The attributes attached to the element.  If
         * there are no attributes, it shall be an empty
         * Attributes object.
         * @throws org.xml.sax.SAXException Any SAX exception, possibly
         *                                  wrapping another exception.
         * @see org.xml.sax.ContentHandler#startElement
         */
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            sb = new StringBuilder();
        }

        /**
         * Receive notification of the end of an element.
         * <p>By default, do nothing.  Application writers may override this
         * method in a subclass to take specific actions at the end of
         * each element (such as finalising a tree node or writing
         * output to a file).</p>
         * @param uri The Namespace URI, or the empty string if the
         * element has no Namespace URI or if Namespace
         * processing is not being performed.
         * @param localName The local name (without prefix), or the
         * empty string if Namespace processing is not being
         * performed.
         * @param qName The qualified name (with prefix), or the
         * empty string if qualified names are not available.
         * @throws org.xml.sax.SAXException Any SAX exception, possibly
         *                                  wrapping another exception.
         * @see org.xml.sax.ContentHandler#endElement
         */
        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            String content = sb.toString().trim();
            
            if(qName.equals(ID_ELEMENT_NAME)) {
                ontologyId = content;
            }
            else if(qName.equals(DISPLAY_NAME_ELEMENT_NAME)) {
                displayName = content;
            }
            else if(qName.equals(ABBREV_ELEMENT_NAME)) {
                abbrevName = content;
            }
            else if(qName.equals(VERSION_ELEMENT_NAME)) {
                internalVersionNumber = content;
            }
            else if(qName.equals(ENTRY_ELEMENT)) {
                entries.add(new BioPortalRepositoryEntry(BioPortalOntologyRepository.this, displayName, ontologyId, abbrevName, format, internalVersionNumber, apiKey));
            }
            else if(qName.equals(FORMAT_ELEMENT_NAME)) {
                format = content;
            }
        }

        /**
         * Receive notification of character data inside an element.
         * <p>By default, do nothing.  Application writers may override this
         * method to take specific actions for each chunk of character data
         * (such as adding the data to a node or buffer, or printing it to
         * a file).</p>
         * @param ch The characters.
         * @param start The start position in the character array.
         * @param length The number of characters to use from the
         * character array.
         * @throws org.xml.sax.SAXException Any SAX exception, possibly
         *                                  wrapping another exception.
         * @see org.xml.sax.ContentHandler#characters
         */
        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            sb.append(ch, start, length);
        }

        public List<BioPortalRepositoryEntry> getEntries() {
            return entries;
        }
    }

}
